import sys
import os
pyfile = (sys.platform[:3] == 'win' and 'python.exe') or 'python'
pypath = sys.executable


def fixWindowsPath(cmdline):
    splitline = cmdline.lstrip().split(' ')
    fixedpath = os.path.normpath(splitline[0])
    return ' '.join([fixedpath] + splitline[1:])


class LaunchMode:
    def __init__(self, label, command):
        self.what = label
        self.where = command

    def __call__(self):
        self.announce(self.what)
        self.run(self.where)

    def announce(self, text):
        print(text)

    def run(self, cmdline):
        assert False, 'run must be defined'


class System(LaunchMode):
    def run(self, cmdline):
        cmdline = fixWindowsPath(cmdline)
        os.system('%s %s' % (pypath, cmdline))


class Popen(LaunchMode):
    """
    run shell command line in a new process
    caveat: may block caller, since pipe closed too soon
    """

    def run(self, cmdline):
        cmdline = fixWindowsPath(cmdline)
        os.popen(pypath + ' ' + cmdline)  # assume nothing to be read


class Fork(LaunchMode):
    """
    run command in explicitly created new process
    for Unix-like systems only, including cygwin
    """

    def run(self, cmdline):
        assert hasattr(os, 'fork')
        cmdline = cmdline.split()  # convert string to list
        if os.fork() == 0:         # start new child process
            os.execvp(pypath, [pyfile] + cmdline)  # run new program in child


class Start(LaunchMode):
    """
     run command independent of caller
     for Windows only: uses filename associations
     """

    def run(self, cmdline):
        assert sys.platform[:3] == 'win'
        cmdline = fixWindowsPath(cmdline)
        os.startfile(cmdline)


class StartArgs(LaunchMode):
    """
    for Windows only: args may require real start forward slashes are okay here
    """

    def run(self, cmdline):
        assert sys.platform[:3] == 'win'
        os.system('start ' + cmdline)  # may create pop-up window


class Spawn(LaunchMode):
    """ 
    run python in new process independent of caller
    for Windows or Unix; use P_NOWAIT for dos box; 
    forward slashes are okay here 
    """

    def run(self, cmdline):
        os.spawnv(os.P_DETACH, pypath, (pyfile, cmdline))


class Top_level(LaunchMode):
    """ 
    run in new window, same process tbd: requires GUI class info too 
    """

    def run(self, cmdline):
        assert False, 'Sorry - mode not yet implemented'


#
# pick a "best" launcher for this platform
# may need to specialize the choice elsewhere
#
if sys.platform[:3] == 'win':
    PortableLauncher = Popen
else:
    PortableLauncher = Fork


class QuietPortableLauncher(PortableLauncher):
    def announce(self, text):
        pass


def selftest():
    file = 'echo.py'
    input('default mode...')
    launcher = PortableLauncher(file, file)
    launcher()  # no block
    input('system mode...')
    System(file, file)()
    if sys.platform[:3] == 'win':
        input('DOS start mode...')  # no block
        StartArgs(file, file)()


if __name__ == '__main__':
    selftest()
